﻿// <copyright file="DataSection.cs" company="Sinclair Community College">
// Copyright 2010 Sinclair Community College
// </copyright>
// <originalAuthor>Patrick S. Seymour (patrick.seymour@gmail.com)</originalAuthor>

namespace SinclairCC.AppV
{
    using System;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>
    /// This class represents a data section in a CP file.
    /// </summary>
    /// <remarks>
    /// Each data section inside the CP file format consists of a section header (40 bytes)
    /// and one or more data pages.
    /// </remarks>
    internal class DataSection
    {
        /// <summary>
        /// An identifying code for the type of data contained in the section.
        /// </summary>
        private SectionIdentifierCode sectionId;

        /// <summary>
        /// This field is reserved and is initialized to all zeroes.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "This field is marked as reserved in the App-V File Format Specification.")]
        private byte[] reserved;

        /// <summary>
        /// The total number of bytes for the section data.
        /// </summary>
        private int sectionSize;

        /// <summary>
        /// The byte offset to the first data page for this section inside the CP file.
        /// </summary>
        /// <remarks>
        /// This value is relative to the start of the CP file.
        /// </remarks>
        private uint firstPageOffset;

        /// <summary>
        /// The absolute byte offset to the next section header inside the CP file.
        /// </summary>
        /// <remarks>
        /// This value is relative to the start of the CP file.
        /// If there are no more sections in the linked-list, the field value will be
        /// initialized to 0xFFFFFFFF.
        /// </remarks>
        private uint nextSectionOffset;

        /// <summary>
        /// A binary file reader that points to the CP file.
        /// </summary>
        private System.IO.BinaryReader fileReader;

        /// <summary>
        /// Initializes a new instance of the DataSection class.
        /// </summary>
        /// <param name="fileReader">
        /// A binary file reader that points to the CP file.
        /// </param>
        public DataSection(System.IO.BinaryReader fileReader)
        {
            this.fileReader = fileReader;
            this.ReadSection();
        }

        /// <summary>
        /// Gets the identifying code for the type of data contained in the section.
        /// </summary>
        public SectionIdentifierCode SectionType
        {
            get { return this.sectionId; }
        }

        /// <summary>
        /// Gets the byte offset to the first data page for this section inside the CP file.
        /// </summary>
        /// <remarks>
        /// This value is relative to the start of the CP file.
        /// </remarks>
        public uint FirstPageOffset
        {
            get { return this.firstPageOffset; }
        }

        /// <summary>
        /// Gets the absolute byte offset of the next section header inside the CP file.
        /// </summary>
        /// <remarks>
        /// This value is relative to the start of the CP file.
        /// </remarks>
        public uint NextSectionOffset
        {
            get { return this.nextSectionOffset; }
        }

        /// <summary>
        /// Gets the total number of bytes for the section data.
        /// </summary>
        public int SectionSize
        {
            get { return this.sectionSize; }
        }

        /// <summary>
        /// Creates a MemoryStream containing the raw data from this section of the
        /// CP file.
        /// </summary>
        /// <returns>
        /// Returns a MemoryStream containing the raw section data.
        /// </returns>
        public System.IO.MemoryStream GetSectionData()
        {
            System.IO.MemoryStream dataStream = new System.IO.MemoryStream();
            this.fileReader.BaseStream.Seek(this.firstPageOffset, System.IO.SeekOrigin.Begin);
            int bytesToRead = this.sectionSize;
            do
            {
                DataPage page = new DataPage(this.fileReader, Math.Min(1024, bytesToRead));
                dataStream.Write(page.Data, 0, page.Data.Length);
                bytesToRead -= page.Data.Length;
                if (page.NextPageOffset != 0xFFFFFFFF)
                {
                    this.fileReader.BaseStream.Seek(page.NextPageOffset, System.IO.SeekOrigin.Begin);
                }
            }
            while (bytesToRead > 0);

            // Seek to the beginning of the stream.
            dataStream.Seek(0, System.IO.SeekOrigin.Begin);

            return dataStream;
        }

        /// <summary>
        /// Reads the actual data for this section from the file.
        /// </summary>
        private void ReadSection()
        {
            ushort rawSectionId = this.fileReader.ReadUInt16();
            if (Enum.IsDefined(typeof(SectionIdentifierCode), rawSectionId))
            {
                this.sectionId = (SectionIdentifierCode)rawSectionId;
            }

            this.reserved = this.fileReader.ReadBytes(26);
            this.sectionSize = this.fileReader.ReadInt32();
            this.firstPageOffset = this.fileReader.ReadUInt32();
            this.nextSectionOffset = this.fileReader.ReadUInt32();
        }
    }
}
